﻿/* 
 * Copyright (c) 2009 Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor: Nathan Letwory
 * 
 */

using System;
using System.Security.Cryptography;
using System.Net;
using System.Threading;
using Mono.Math;
using Mono.Security.Cryptography;

namespace VerseSharp
{
    public class VerseSession
    {
        protected string username;
        protected string password;
        protected uint avatarID;
        protected RSAManaged loginRSA;
        public byte[] xorKey;
        public bool xorKeySet;
        protected IPEndPoint verseServer;
        protected Verse verse;

        public VerseSession(Verse verse, IPEndPoint verseServer, string username, string password)
        {
            this.verseServer = verseServer;
            this.username = username;
            this.password = password;
            this.verse = verse;
            
            #region Register Callbacks

            this.verse.OnConnect1 += Connect1Handler;
            this.verse.OnConnectAccept += ConnectAcceptHandler;

            #endregion Register Callbacks
        }

        /// <remarks>rewrite</remarks>
        public void Start()
        {

            // Generate an RSA keypair, used for login
            loginRSA = new RSAManaged(VerseConstants.ENCRYPTION_KEY_BITS);
            RSAParameters rsaParams = loginRSA.ExportParameters(false);

            // Initialize the XOR key, used for encrypting normal traffic
            xorKey = new byte[VerseConstants.ENCRYPTION_KEY_SIZE];
            xorKeySet = false;

            // Send the stage 0 connect command
            ConnectCommand connect = new ConnectCommand();
            connect.Stage = 0;
            connect.Exponent = rsaParams.Exponent;
            connect.Modulus = rsaParams.Modulus;

            verse.SendCommand(this, connect);
        }

        // used in client API
        public uint AvatarID
        {
            get
            {
                return avatarID;
            }
        }
        
        public IPEndPoint VerseServer
        {
            get
            {
                return verseServer;
            }
        }

        // used in client API
        public String AvatarName
        {
            get;
            set;
        }

        public void SetIndexSubscriptions(NodeTypes mask)
        {
            NodeIndexSubscribeCommand indexSubscribe = new NodeIndexSubscribeCommand();
            indexSubscribe.Mask = mask;
            verse.SendCommand(this, indexSubscribe);
        }

        public void SetNodeSubscription(uint nodeID, bool subscribe)
        {
            NodeSubscribeCommand nodeSubscribe = new NodeSubscribeCommand();
            nodeSubscribe.NodeID = nodeID;
            nodeSubscribe.Subscribed = subscribe;
            verse.SendCommand(this, nodeSubscribe);
        }

        public void SetGeometryLayerSubscription(uint nodeID, ushort layerID, RealFormat precision, bool subscribe)
        {
            GeometryLayerSubscribeCommand geometrySubscribe = new GeometryLayerSubscribeCommand();
            geometrySubscribe.NodeID = nodeID;
            geometrySubscribe.LayerID = layerID;
            geometrySubscribe.Precision = precision;
            geometrySubscribe.Subscribed = subscribe;
            verse.SendCommand(this, geometrySubscribe);
        }

        public void SetBitmapLayerSubscription(uint nodeID, ushort layerID, byte level)
        {
            Console.WriteLine("Subscribing to bitmap layer {0} on {1} with level {2}", layerID, nodeID, level);
            BitmapLayerSubscribeCommand bitmapLayerSubscribe = new BitmapLayerSubscribeCommand();
            bitmapLayerSubscribe.NodeID = nodeID;
            bitmapLayerSubscribe.LayerID = layerID;
            bitmapLayerSubscribe.Level = level;
        }

        public void SetObjectTransformSubscription(uint nodeID, RealFormat precision, bool subscribe)
        {
            Console.WriteLine(String.Format("Subscribing to object transformation on {0} with precision {1}", nodeID, precision));
            ObjectTransformSubscribeCommand objectTransformSubscribe = new ObjectTransformSubscribeCommand();
            objectTransformSubscribe.NodeID = nodeID;
            objectTransformSubscribe.Precision = precision;
            objectTransformSubscribe.Subscribed = subscribe;
            verse.SendCommand(this, objectTransformSubscribe);
        }

        /// <summary>
        /// Subscribe or unsubscribe to the specified tag group
        /// </summary>
        /// <param name="nodeID">Node where the tag group resides</param>
        /// <param name="groupID">Tag group to subscribe to or unsubscribe from</param>
        /// <param name="subscribe">True to subscribe, false to unsubscribe</param>
        public void SetTagGroupSubscription(uint nodeID, ushort groupID, bool subscribe)
        {
            Console.WriteLine("{0}ubscribe to tag group {1}:{2}", subscribe ? "S" : "Uns", nodeID, groupID);
            TagGroupSubscribeCommand tagGroupSubscribe = new TagGroupSubscribeCommand();
            tagGroupSubscribe.NodeID = nodeID;
            tagGroupSubscribe.GroupID = groupID;
            tagGroupSubscribe.Subscribed = subscribe;
            verse.SendCommand(this, tagGroupSubscribe);
        }




        private void Connect1Handler(ConnectCommand command)
        {
            if (command.Version != VerseConstants.VERSE_VERSION)
            {
                LogAdapter.Error(String.Format("Server is using version {0} of the verse protocol", command.Version));
                return;
            }

            // Create a buffer for storing the username/password and initialize it with random data
            byte[] namePass = new byte[VerseConstants.ENCRYPTION_KEY_SIZE];
            Random rand = new Random();
            rand.NextBytes(namePass);

            // Copy the username and password into the buffer
            DataConverter.BigEndian.PutBytes(namePass, 0, username);
            DataConverter.BigEndian.PutBytes(namePass, 32, password);
            // Make sure the last byte is zero (required by the protocol)
            namePass[namePass.Length - 1] = 0;

            // Encrypt the username/password data
            RSAParameters rsaParams = new RSAParameters();
            rsaParams.Exponent = command.Exponent;
            rsaParams.Modulus = command.Modulus;
            RSAManaged rsa = new RSAManaged(512);
            rsa.ImportParameters(rsaParams);
            namePass = rsa.EncryptValue(namePass);

            // Send the stage 2 connection command (login)
            ConnectCommand connect = new ConnectCommand();
            connect.Stage = 2;
            connect.EncryptedData = namePass;
            verse.SendCommand(this, connect);
        }

        private void ConnectAcceptHandler(ConnectAcceptCommand accept)
        {
            LogAdapter.Info("Connection accepted, AvatarID=" + accept.AvatarID.ToString());
            this.avatarID = accept.AvatarID;

            // Decrypt the XOR key
            xorKey = loginRSA.DecryptValue(accept.EncryptedXORKey);
            xorKeySet = true;
        }
    }
}
